#pragma once
/*
    http协议定制
*/
#include "log.hpp"
#include "Utility.hpp"
#include <vector>
#include <unordered_map>
#include <sstream>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <algorithm>
#define NOT_FOUND 404
#define OK 200
#define SERVER_ERROR 500
#define BAD_REQUEST 400 // 错误请求
//#define _DEBUG_
#define BLANK_LINE "\n"
const char *webroot_ = "wwwroot";     // web根目录，访问网站的所有资源都存放在这个目录下
const char *homepage = "index.html";  // 每个目录下的index.html
const char *login = "blog/blog_html/blog_login.html";     // 博客登陆页
const char *notfound = "404.html";    // 不存在资源返回的页面
const char *badrequest = "400.html";  // 错误请求页面
const char *serverError = "500.html"; // 服务器错误页面
extern Log lg;

// 通过后缀获取content-type
static std::string SuffixDesc(const std::string &suffix)
{
    // 通过后缀跟类型对应表获取对应类型
    static std::unordered_map<std::string, std::string> suffix_type;
    suffix_type.insert({".html", "text/html"});
    suffix_type.insert({".js", "application/javascript"});
    suffix_type.insert({".css", "text/css"});
    suffix_type.insert({".jpg", "image/jpeg "});
    suffix_type.insert({".png", "image/png "});
    suffix_type.insert({".json","application/json"});
    auto iter = suffix_type.find(suffix);
    if (iter != suffix_type.end())
    {
        return iter->second;
    }
    return "UnKnow";
}
// 输入状态码输出对应的状态描述
std::string PasingCode(int code)
{
    std::string StateDescription;
    switch (code)
    {
    case 200:
        StateDescription = "OK";
        break;
    case 400:
        StateDescription = "BAD REQUEST";
        break;
    case 404:
        StateDescription = "NOT FOUND";
        break;
    case 500:
        StateDescription = "SERVER ERROR";
        break;
    }
    return StateDescription;
}

// 请求类
class Request
{
public:
    std::string request_line;              // 请求行
    std::vector<std::string> request_head; // 请求头
    std::string blank_line;                // 空行
    std::string request_body;              // 请求正文
public:
    // 解析后的请求、其他标志符
    std::string method_;                                  // 请求方法
    std::string uri_;                                     // 请求的uri
    std::string version_;                                 // http协议版本
    std::unordered_map<std::string, std::string> kv_head; // kv结构的请求头
    int length_;                                          // 如果有正文，正文的长度
    std::string content_type;      //请求正文的类型
    std::string path_;                                    // 访问服务端的真实路径
    std::string query_string;                             // Get请求方法携带的参数
    bool cgi_;                                            // 用来判断是否访问的是一个程序 (是否执行cgi机制)
    std::string suffix_;                                  // 请求的资源的后缀
    std::string authorization_;  //用来保存token的授权验证
public:
    Request() : length_(0), cgi_(false) {}
    ~Request() {}
};
// 响应类
class Response
{
public:
    std::string status_line;                // 状态行
    std::vector<std::string> response_head; // 响应头
    std::string blank_line;                 // 空行
    std::string response_body;              // 响应正文
public:
    // 根据request得到的信息
    size_t size_;    // 只用于获取访问的是静态资源的文件的字节数
    int status_code; // 根据请求的资源设置的状态码 ; 404 表示资源不存在返回错误首页
    int response_fd; // 这个fd打开的是需要返回的静态资源文件
public:
    Response() : size_(0), status_code(200), response_fd(-1) {}
    ~Response() {}
};
// 获取请求、解析请求、构建响应类
class ReqtoResponse
{
private:
    int fd_; // 从这个fd里面读取或发送
    Request req_;
    Response resp_;
    bool stop_; // 用来判断还是否需要往下执行
public:
    ReqtoResponse(const int &fd) : fd_(fd), stop_(false) {}
    ~ReqtoResponse()
    {
        close(fd_); // 完成就关闭fd
    }

private:
    // 读取请求行到request对象内
    bool ReadLine()
    {
        int flag = Utility::GetLine(fd_, req_.request_line);
        if (flag != 0)
        {
            return false;
        }
        return true;
    }
    // 读取请求头到request对象内
    bool ReadHead()
    {
        std::string line;
        while (line != "\n") // 读取到空行结束
        {
            line.clear(); // 读取前清空
            if (Utility::GetLine(fd_, line) != 0)
                return false;
            req_.request_head.push_back(line);
        }
        // 把空行读取到request
        req_.blank_line = line;
        return true;
    }
    // 解析请求行 -> 得到 方法、uri、协议版本
    // get /a/b/c http/1.1 ->  method:get  uri: /a/b/c  version: http/1.1
    void PasingLine()
    {
        auto msg = req_.request_line;
        std::stringstream ss(msg);
        auto &method = req_.method_;
        ss >> method >> req_.uri_ >> req_.version_; // 默认以空格切分

        // 前处理大小写
        std::transform(method.begin(), method.end(), method.begin(), ::toupper);
    }
    // 解析请求头-> 打散为一个个kv结构
    void PasingHead()
    {
        for (auto &e : req_.request_head)
        {
            std::string key;
            std::string value;
            if (Utility::CutString(e, key, value, ": "))
            {
                req_.kv_head.insert({key, value});
            }
        }
        //如果有授权验证
        auto it=req_.kv_head.find("Authorization");
        if(it!=req_.kv_head.end())
        {
            //有授权验证
            req_.authorization_=it->second;
           // std::cerr<<"DEBUG:READ HEAD!!:Authorization:"<<req_.authorization_;
        }
    }
    // 判断是否有请求正文需要读取上来
    bool IsNeedBody()
    {
        // 这里如果有直接把正文的长度写入到请求对象的length里面;
        auto method = req_.method_;
        if (method == "POST")
        {
            int flag=0;
            //获取正文长度
            auto it = req_.kv_head.find("Content-Length");
            if (it != req_.kv_head.end())
            {
                // 有请求正文
                req_.length_ = atoi(it->second.c_str());
                flag=1;
            }

            //获取正文类型
            auto it2=req_.kv_head.find("Content-Type");
            if(it2!=req_.kv_head.end())
            {
                req_.content_type=it2->second;

            }
            return flag;
        }
        return false;
    }
    // 把请求正文读取到request对象的requeset_body里面
    int ReadBody()
    {
        if (IsNeedBody())
        {
            // 走到这里说明请求方法为POST且需要读取正文且正文长度已经写入到request的length里面
            auto len = req_.length_;
            auto &body = req_.request_body;
            char ch;
            while (len--)
            {
                ssize_t n = recv(fd_, &ch, 1, 0); // 为什么要一个一个读？一次读len长度不行吗？
                if (n > 0)
                {
                    // 读取成功
                    body.push_back(ch);
                }
                else if (n == 0)
                {
                    // 写端关闭
                    lg(INFO, __LINE__, "Write close!!");
                    return -1;
                }
                else
                {
                    // 读取错误
                    lg(ERROR, __LINE__, "Read error!!!");
                    return -2;
                }
            }
        }
        return 0;
    }
    int IsCgiProgram()
    {
                lg(DEBUG, __LINE__, "cgi!!");

        /*
            1.创建子进程,让子进程进行程序替换
            2.利用双管道父子进程间进行信息的传递(父传参数给子,子把处理结果传给父)
            3.如果是uri传参利用环境变量传(因为比较短),如果是post传参利用管道传,(因为比较多)
        */
        auto &code = resp_.status_code;
        auto &path = req_.path_;
        auto &method = req_.method_;
        auto &body = req_.request_body;
        auto &query_string = req_.query_string;
        auto &content_type=req_.content_type;
        auto &Authorization=req_.authorization_;
        int len = req_.length_;
        std::string method_env;
        std::string query_string_env;
        std::string content_length_env;
        std::string content_type_env;
        std::string authorization_env;
        // 1.先创建双管道,站在父进程的视角
        int input[2], output[2];
        int i = pipe(input);
        int o = pipe(output);
        if (i < 0 || o < 0)
        {
            lg(ERROR, __LINE__, "Pipe error!!");
            return SERVER_ERROR;
        }
        // 2. 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
                //lg(DEBUG, __LINE__, "进入子进程!!");

            // 子进程
            close(input[0]);
            close(output[1]);

            // 设置method的环境变量让子进程继承,将来子进程先通过method环境变量获取是GET还是POST再决定从环境变量里读还是从管道读数据

            //    std::string len="CONTENT_LENGTH=";
            //    len+=std::to_string(req_.length_);
            //    putenv((char*)len.c_str());
            method_env = "METHOD=";
            method_env += method;
            putenv((char *)method_env.c_str());
            if ("GET" == method)
            {
                // 把参数添加到环境变量中
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                std::cout << "DEBUG:" << query_string_env << std::endl;
                if(!Authorization.empty())
                {
                    authorization_env="Authorization=";
                    authorization_env+=Authorization;
                    putenv((char*)authorization_env.c_str());
                   // lg(DEBUG,__LINE__,"authorization_env is : %s",authorization_env.c_str());
                }
                if ((putenv((char *)query_string_env.c_str())) != 0)
                {
                    lg(ERROR, __LINE__, "putenv error!!");
                    exit(-2);
                }
                //lg(DEBUG, __LINE__, "GET METHOD,Add query_string  env");
            }

            // std::string len="CONTENT_LENGTH=";
            //    len+=std::to_string(req_.length_);
            //    putenv((char*)len.c_str());
            else if ("POST" == method)
            {
                    if(!Authorization.empty())
                {
                    authorization_env="Authorization=";
                    authorization_env+=Authorization;
                    putenv((char*)authorization_env.c_str());
                  //  lg(DEBUG,__LINE__,"authorization_env is : %s",authorization_env.c_str());
                }

                // 如果是POST方法子进程需要通过管道获取参数,要知道获取的参数的字节数,再传一个环境变量
                //  std::string len="CONTENT_LENGTH=";
                //  len+=std::to_string(req_.length_);
                //  std::cout<<"DEBUG:"<<len<<std::endl;
                //  putenv((char*)len.c_str());// const char*可以转成 char* ?
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(len);
                putenv((char *)content_length_env.c_str());

                //类型也要传入
                content_type_env="CONTENT_TYPE=";
                content_type_env+=content_type;
                putenv((char*)content_type_env.c_str());

                //lg(INFO, __LINE__, "POST METHOD, add Content_Length env");
            }
            else
            {
                // do nothing
            }

            // 重定向工作,规定子进程往后只需要往标准流读写
            dup2(input[1], 1);
            dup2(output[0], 0);
            execl(path.c_str(), path.c_str(), NULL);
            exit(-1); // 进程替换失败结束进程
        }
        else if (id < 0)
        {
            lg(ERROR, __LINE__, "Fork error!!");
            return SERVER_ERROR;
        }
        else
        {
            // 父进程,从input[0]读,从output[1]写
            close(input[1]);
            close(output[0]);

            // 如果是post方法把参数写入管道
            if ("POST" == method)
            {
               
                // 管道能存放内容有限,需要记录每次写多少,还有多少没写
                int len = req_.length_; // 总共要写的字节
                int size = 0;           // 当次写入字节数
                int total = 0;          // 总共已写入字节数
                while (total < len)
                {
                    size = write(output[1], body.c_str() + total, len - total);
                    if (size > 0)
                    {
                        total += size;
                    }
                }
            }
            // 父进程从管道里读到的内容是子进程程序替换后运行的结果,把结果直接写入response的正文中
            char ch;
            while (read(input[0], &ch, 1) > 0)
            {
                resp_.response_body.push_back(ch);
            }
            // std::cout << resp_.response_body << std::endl;
            int status;
            pid_t pid = waitpid(id, &status, 0);
            // 对状态码进行判断
            if (pid == id)
            {
                if (WIFEXITED(status)) // 检测是否正常退出
                {
                    if (WEXITSTATUS(status) == 0) // 正常退出还要看退出码是否为0
                        return OK;
                    else
                        return SERVER_ERROR;
                }
                else
                    return SERVER_ERROR;
            }
            close(input[0]);
            close(output[1]);
        }
        return OK;
    }
    int NonCgiProgram()
    {
        // 如果不是cgi程序就返回静态资源,怎么返回? -> 把文件打开,读取文件,把内容拷贝到TCP发送缓冲区
        resp_.response_fd = open(req_.path_.c_str(), O_RDONLY);
        if (resp_.response_fd < 0)
        {
            // 走到这里说明打开静态资源文件失败
            lg(ERROR, __LINE__, "Open response_fd error!!");
            return SERVER_ERROR;
        }
        return OK;
    }

    // 构建正常响应报头
    void buildOkResponse(bool cgi)
    {
        // 响应报头
        auto &rp_head = resp_.response_head;
        std::string type = "Content-Type: ";
        if(req_.content_type=="application/json")
        {
            type+=req_.content_type;
            type+="\n";
        }
        else
        {
            type += SuffixDesc(req_.suffix_);
        type += "\n";
        }
        
        rp_head.push_back(type);
        std::string len = "Content-Length: ";
        if (cgi)
        {
            len += std::to_string(resp_.response_body.size());
        }
        else
        {
            len += std::to_string(resp_.size_);
        }
        len += "\n";
        rp_head.push_back(len);
    }

    // 构建错误响应报头
    bool buildNotFound(const std::string &filename)
    {
        auto &fd = resp_.response_fd;
        // 1.打开404页面资源,获取fd
        fd = open(filename.c_str(), O_RDONLY);
        if (fd < 0)
        {
            return false;
        }
        else
        {
            // 2.获取404页面资源大小(字节数)
            struct stat st;
            stat(filename.c_str(), &st);
            resp_.size_ = st.st_size;
        }
        return true;
    }
    bool buildBadRequest(const std::string &filename)
    {
        auto &fd = resp_.response_fd;
        // 1.打开400页面资源,获取fd
        fd = open(filename.c_str(), O_RDONLY);
        if (fd < 0)
        {
            return false;
        }
        else
        {
            // 2.获取400页面资源大小(字节数)
            struct stat st;
            stat(filename.c_str(), &st);
            resp_.size_ = st.st_size;
        }
        return true;
    }
    bool buildServerError(const std::string &filename)
    {
        auto &fd = resp_.response_fd;
        // 1.打开500页面资源,获取fd
        fd = open(filename.c_str(), O_RDONLY);
        if (fd < 0)
        {
            return false;
        }
        else
        {
            // 2.获取500页面资源大小(字节数)
            struct stat st;
            stat(filename.c_str(), &st);
            resp_.size_ = st.st_size;
        }
        return true;
    }

    bool BuildResponseHandler()
    {
        auto &statusLine = resp_.status_line;
        auto &cgi = req_.cgi_;
        // 状态行: 版本+状态码+状态描述 -> HTTP/1.1 200 OK
        statusLine += "HTTP/1.1";
        statusLine += " ";
        statusLine += std::to_string(resp_.status_code);
        statusLine += " ";
        statusLine += PasingCode(resp_.status_code);
        statusLine += "\n";
        // 根据状态码构建响应报头
        std::string path = webroot_;
        path += "/";
        switch (resp_.status_code)
        {
        case 200:
            buildOkResponse(cgi);
            break;
        case 404:
            path += notfound;
            if (!buildNotFound(path))
                return false;
            break;
        case 400:
            path += badrequest;
            if (!buildBadRequest(path))
                return false;
            break;
        case 500:
            path += serverError;
            if (!buildServerError(path))
                return false;
            break;
        }
        // 空行
        resp_.blank_line = BLANK_LINE;

        // 响应正文已经处理了
        return true;
    }

public:
    bool Stop()
    {
        return stop_;
    }
    // 获取请求接口（包括解析）
    void ObtaintheRequest()
    {
        if (ReadLine())
        {
            if (ReadHead())
            {
                PasingLine();
                PasingHead();
                if (ReadBody() != 0)
                {
                    stop_ = true;
                }
            }
            else
                stop_ = true;
        }
        else
            stop_ = true;
    }
    // 构建响应接口
    bool BuildResponse()
    {
               // lg(DEBUG, __LINE__, "进入构建响应!!");

        // 1.目前只处理GET和POST请求，先判断是否为GET/POST请求
        auto &method = req_.method_;

        auto uri = req_.uri_;
        auto &path = req_.path_;
        auto &code = resp_.status_code;
        auto &suffix = req_.suffix_;
        size_t suffixpos; // 查找后缀的下标

        if (method != "GET" && method != "POST")
        {
            code = BAD_REQUEST;
            lg(ERROR, __LINE__, "Request error!!");
            goto END;
        }
        /*
         2.如果是GET/POST 第一步处理访问的URI,把它变成访问当前服务器下的web目录下的资源文件
        */
        if ("POST" == method)
        {
            // 处理POST请求的uri,只要是POST请求它的参数就是放在正文,uri不存参数
            path = webroot_;
            path += uri;
            // POST请求都认为访问cgi程序
            req_.cgi_ = true;
        }
        if ("GET" == method)
        {
            // 处理GET方法的uri,首先判断是否带参，如果带参左边为路径右边为参数
            size_t pos = uri.find("?");
            path = webroot_;

            if (pos != std::string::npos)
            {
                // 带参
                std::string temp;
                Utility::CutString(uri, temp, req_.query_string, "?");
                path += temp;
                // 带参的认为都是要访问cgi程序
                req_.cgi_ = true;
            }
            else
            {
                // 不带参
                path += uri;
            }
        }
        /*
             3.把所有以'/'结尾的uri都处理为访问web的首页
        */
        if (path.back() == '/') // uri可能会这样:/a/b/c/?a=100&b=200,最后一个不是'/',但他访问的路径最后一个为'/'
        {
            // 存在以'/'结尾的path
            path = webroot_;
            path += "/";
            path += login;
        }
        /*
            4.判断资源是否存在,如果不存在返回404 页面;
            如果存在判断是什么资源,对不同资源进行不同处理;
        */
        std::cout << path << std::endl;
        struct stat st;                   // 用来保存获取到的文件的属性信息
        if (stat(path.c_str(), &st) == 0) // 注意这里资源不存在返回-1,存在返回0!!
        {
            // lg(DEBUG,"File is exist!");
            // 资源存在
            if (S_ISDIR(st.st_mode)) // 判断是否目录
            {
                // 如果是个目录给它添加这个目录下的index.html文件
                path += '/';
                path += homepage;           // 这行代码的前提最好每个目录下都有一个homepage!!否则返回不存在资源页面
               if( stat(path.c_str(), &st)!=0)
               {
                // 注意这里如果加上homepage后不存在返回404页面
                //std::cerr<<"DEBUG -1"<<std::endl;
                 lg(ERROR, __LINE__, "File Not Found!!,errno is :%d,errstring is: %s", errno, strerror(errno));

                code=NOT_FOUND;
                goto END;
               } 
            }

            // 以下任意一个条件成立都是可执行程序
            if (S_IXUSR & st.st_mode || S_IXGRP & st.st_mode || S_IXOTH & st.st_mode)
            {
                // 需要执行cgi机制
                req_.cgi_ = true;
            }
            //  如果资源存在返回资源的字节数
            resp_.size_ = st.st_size; // 如果是可执行程序也要算它的大小吗?
        }
        else
        {
            // 资源不存在,设置错误码
                std::cerr<<"DEBUG -1"<<std::endl;

            lg(ERROR, __LINE__, "File Not Found!!,errno is :%d,errstring is: %s", errno, strerror(errno));
            code = NOT_FOUND;
            goto END;
        }

        // 处理请求文件的后缀
        suffixpos = path.rfind(".");
        if (suffixpos != std::string::npos)
        {
            // 找到
            req_.suffix_ = path.substr(suffixpos);
        }

#ifdef _DEBUG_
        // 测试读取上来并解析后的请求：
        std::cout << "Line:\n"
                  << req_.request_line;
        std::cout << "Head:" << std::endl;
        for (auto &e : req_.kv_head)
        {
            std::cout << "K: " << e.first << " " << "V: " << e.second;
        }
        std::cout << "Blankline:\n";
        std::cout << req_.blank_line;
        std::cout << "Body:\n";
        std::cout << req_.request_body << std::endl;
        std::cout << "Method: " << req_.method_
                  << " " << "Uri: " << req_.uri_
                  << " " << "Version: " << req_.version_
                  << " " << "Length: " << req_.length_
                  << " " << "Path: " << req_.path_
                  << " " << "QueryString: " << req_.query_string
                  << " " << "Cgi: " << req_.cgi_
                  << " " << "ContentSize: " << resp_.size_ << std::endl;
#endif
        if (req_.cgi_)
        {
            // 执行cgi程序
            code = IsCgiProgram();
        }
        else
        {
            // 不执行cgi程序,返回静态资源
            code = NonCgiProgram();
        }
    END:

        return BuildResponseHandler();
    }
    // 发送响应接口
    void SendResponse()
    {
        // 1.发送状态行
        send(fd_, resp_.status_line.c_str(), resp_.status_line.size(), 0);
        // 2.发送报头
        for (auto &e : resp_.response_head)
        {
            send(fd_, e.c_str(), e.size(), 0);
        }
        // 3.发送空行
        send(fd_, resp_.blank_line.c_str(), resp_.blank_line.size(), 0);
        // 4.发送正文
        if (req_.cgi_)
        {
            // 发送body
            const char *body = resp_.response_body.c_str();
            size_t total = 0;                         // 共已发送
            size_t size = resp_.response_body.size(); // 需要发送
            size_t n = 0;                             // 当前已发送
            while (total < size && (n = send(fd_, body + total, size - total, 0)) > 0)
            {
                total += n;
            }
        }
        else
        {
            // 发送文件
            sendfile(fd_, resp_.response_fd, nullptr, resp_.size_);
            close(fd_);
        }
    }
};
// 线程入口函数
// class Entry
// {
// private:
// public:
//     static void *Handler(void *arg)
//     {
//         // lg(DEBUG,"Thread Entry success!!");

//         int fd = *(int *)arg;
//         ReqtoResponse *rtoq = new ReqtoResponse(fd);
//         rtoq->ObtaintheRequest();
//         if(!rtoq->Stop())
//         {
//              rtoq->BuildResponse();
//         rtoq->SendResponse();
//         }

//         delete rtoq;
//         return nullptr;
//     }
// };

// 回调函数
class CallBack
{
public:
    CallBack() {}
    ~CallBack() {}

public:
    void operator()(const int &fd)
    {
        HandlerRequest(fd);
    }
    // 处理请求、构建响应并发送响应的方法
    void HandlerRequest(const int &fd)
    {
        lg.Enable(3);
        ReqtoResponse *rtoq = new ReqtoResponse(fd);
        rtoq->ObtaintheRequest();
               // lg(DEBUG, __LINE__, "进入线程!!");

        if (!rtoq->Stop())
        {
            if (rtoq->BuildResponse())
            {
                rtoq->SendResponse();
            }
            else
            {
                lg(ERROR, __LINE__, "BuildResponse error!!");
            }
        }
        else
        {
            lg(ERROR, __LINE__, "ObtaintheRequest error!!");
        }
        close(fd);
        delete rtoq;
    }
};